#include <iostream>       // 提供“输入输出流操作” ---> cout、cerr
#include <unistd.h>       // 提供“系统调用” ---> pipe、fork、write、read、sleep 等

#include <sys/types.h>    // 提供 pid_t 等类型定义
#include <sys/wait.h>     // 提供 waitpid 函数（等待子进程结束）
#include <cstdio>         // 提供 snprintf 函数（格式化字符串到缓冲区）
#include <cstring>        // 提供 strlen 函数（计算字符串长度）

/*----------------------------------------------------- 辅助函数---> 写的快&读的慢 -----------------------------------------------------*/
//实现：子进程执行的写操作函数：向管道写端写入数据
void ChildWrite(int wfd) //注意：参数 wfd：管道的写端文件描述符
{
    char buffer[1024];    
    int cnt = 0;          

    while (true)          
    {
        snprintf(buffer, sizeof(buffer), "我是子进程, pid: %d, cnt: %d", getpid(), cnt++);
        write(wfd, buffer, strlen(buffer));

        printf("子进程:%d\n",cnt); 
    }
}

//实现：父进程执行的读操作函数：从管道读端读取数据
void FatherRead(int rfd) //注意：参数 rfd：管道的读端文件描述符
{
    char buffer[1024];    
    while (true)          
    {

        buffer[0] = 0;    
        ssize_t n = read(rfd, buffer, sizeof(buffer) - 1);
        if (n > 0)     
        {
            buffer[n] = 0; 
            std::cout << "子进程说：" << buffer << std::endl; 
        }
        
        sleep(1);
    }
}

/*----------------------------------------------------- 主程序 -----------------------------------------------------*/
int main()
{
    /*------------------------------------第一步：创建匿名管道------------------------------------*/
    //1.定义数组fds用于存储管道的两个文件描述符
    int fds[2] = {0};   
    //2.调用pipe系统调用创建管道 ---> 成功返回 0，失败返回 -1
    int n = pipe(fds); 
    //3.判断管道创建是否失败
    if (n < 0)      
    {
        std::cerr << "pipe error" << std::endl; 
        return 1;        
    }

    //4.打印管道的读端和写端文件描述符（通常从 3 开始，因 0、1、2 被标准输入/输出/错误占用）
    std::cout << "fds[0]: " << fds[0] << std::endl;
    std::cout << "fds[1]: " << fds[1] << std::endl;

    /*------------------------------------第二步：创建子进程------------------------------------*/
    //1.调用fork系统调用常见子进程 ---> 父进程返回子进程 PID，子进程返回 0，失败返回 -1
    pid_t id = fork(); 

    /*------------------------------------第三步：关闭无关端 ---> 父进程读、子进程写------------------------------------*/
    //2.子进程的执行分支
    if (id == 0)          
    {
        //2.1：关闭子进程不需要的管道读端(fds[0])，构建单向通信信道
        close(fds[0]);

        //2.2：调用子进程写函数，传入管道写端文件描述符
        ChildWrite(fds[1]);

        //2.3：写操作结束后关闭写端
        close(fds[1]);

        //2.4：子进程退出
        exit(0);         
    }

    //3.父进程的执行分支
    //3.1：关闭子进程不需要的管道写端(fds[1])，构建单向通信信道
    close(fds[1]);

    //3.2：调用父进程读函数，传入管道读端文件描述符
    FatherRead(fds[0]);

    //3.3：等待子进程结束
    waitpid(id, nullptr, 0);  //注意：“父进程等待子进程结束” 要在 “父进程读函数” 之后调用

    //3.4：读操作结束后关闭读端
    close(fds[0]);

    return 0;            
}